#===-- runtime/CMakeLists.txt ----------------------------------------------===#
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
#===------------------------------------------------------------------------===#

if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
  cmake_minimum_required(VERSION 3.20.0)

  project(FlangRuntime C CXX)

  set(CMAKE_CXX_STANDARD 17)
  set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
  set(CMAKE_CXX_EXTENSIONS OFF)

  set(FLANG_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/..")

  set(LLVM_COMMON_CMAKE_UTILS "${FLANG_SOURCE_DIR}/../cmake")
  set(LLVM_CMAKE_UTILS "${FLANG_SOURCE_DIR}/../llvm/cmake")
  set(CLANG_CMAKE_UTILS "${FLANG_SOURCE_DIR}/../clang/cmake")

  # Add path for custom modules
  list(INSERT CMAKE_MODULE_PATH 0
    "${FLANG_SOURCE_DIR}/cmake"
    "${FLANG_SOURCE_DIR}/cmake/modules"
    "${LLVM_COMMON_CMAKE_UTILS}"
    "${LLVM_COMMON_CMAKE_UTILS}/Modules"
    "${LLVM_CMAKE_UTILS}"
    "${LLVM_CMAKE_UTILS}/modules"
    "${CLANG_CMAKE_UTILS}/modules"
    )

  include(AddClang)
  include(AddLLVM)
  include(AddFlang)
  include(HandleLLVMOptions)

  include(TestBigEndian)
  test_big_endian(IS_BIGENDIAN)
  if (IS_BIGENDIAN)
    add_compile_definitions(FLANG_BIG_ENDIAN=1)
  else ()
    add_compile_definitions(FLANG_LITTLE_ENDIAN=1)
  endif ()
  include_directories(BEFORE
    ${FLANG_SOURCE_DIR}/include)
endif()

include(CheckCXXSymbolExists)
include(CheckCXXSourceCompiles)
check_cxx_symbol_exists(strerror_r string.h HAVE_STRERROR_R)
# Can't use symbol exists here as the function is overloaded in C++
check_cxx_source_compiles(
  "#include <string.h>
   int main() {
     char buf[4096];
     return strerror_s(buf, 4096, 0);
   }
  "
  HAVE_DECL_STRERROR_S)

check_cxx_compiler_flag(-fno-lto FLANG_RUNTIME_HAS_FNO_LTO_FLAG)
if (FLANG_RUNTIME_HAS_FNO_LTO_FLAG)
  set(NO_LTO_FLAGS "-fno-lto")
else()
  set(NO_LTO_FLAGS "")
endif()

configure_file(config.h.cmake config.h)
# include_directories is used here instead of target_include_directories
# because add_flang_library creates multiple objects (STATIC/SHARED, OBJECT)
# with different names
include_directories(AFTER ${CMAKE_CURRENT_BINARY_DIR})

append(${NO_LTO_FLAGS} CMAKE_C_FLAGS)
append(${NO_LTO_FLAGS} CMAKE_CXX_FLAGS)

# Disable libstdc++/libc++ assertions, even in an LLVM_ENABLE_ASSERTIONS build,
# to avoid an unwanted dependency on libstdc++/libc++.so.
add_definitions(-U_GLIBCXX_ASSERTIONS)
add_definitions(-U_LIBCPP_ENABLE_ASSERTIONS)

add_subdirectory(FortranMain)

set(sources
  ISO_Fortran_binding.cpp
  allocatable.cpp
  array-constructor.cpp
  assign.cpp
  buffer.cpp
  character.cpp
  command.cpp
  complex-powi.cpp
  complex-reduction.c
  connection.cpp
  copy.cpp
  derived-api.cpp
  derived.cpp
  descriptor-io.cpp
  descriptor.cpp
  dot-product.cpp
  edit-input.cpp
  edit-output.cpp
  environment.cpp
  exceptions.cpp
  execute.cpp
  extensions.cpp
  extrema.cpp
  file.cpp
  findloc.cpp
  format.cpp
  inquiry.cpp
  internal-unit.cpp
  io-api.cpp
  io-error.cpp
  io-stmt.cpp
  iostat.cpp
  main.cpp
  matmul-transpose.cpp
  matmul.cpp
  memory.cpp
  misc-intrinsic.cpp
  namelist.cpp
  non-tbp-dio.cpp
  numeric.cpp
  pointer.cpp
  product.cpp
  ragged.cpp
  random.cpp
  reduction.cpp
  stat.cpp
  stop.cpp
  sum.cpp
  support.cpp
  temporary-stack.cpp
  terminator.cpp
  time-intrinsic.cpp
  tools.cpp
  transformational.cpp
  type-code.cpp
  type-info.cpp
  unit-map.cpp
  unit.cpp
  utf.cpp
)

option(FLANG_EXPERIMENTAL_CUDA_RUNTIME
  "Compile Fortran runtime as CUDA sources (experimental)" OFF
  )

# List of files that are buildable for all devices.
set(supported_files
  ISO_Fortran_binding.cpp
  allocatable.cpp
  array-constructor.cpp
  assign.cpp
  character.cpp
  copy.cpp
  derived-api.cpp
  derived.cpp
  descriptor.cpp
  dot-product.cpp
  extrema.cpp
  findloc.cpp
  inquiry.cpp
  matmul-transpose.cpp
  matmul.cpp
  memory.cpp
  misc-intrinsic.cpp
  numeric.cpp
  pointer.cpp
  product.cpp
  ragged.cpp
  stat.cpp
  sum.cpp
  support.cpp
  terminator.cpp
  tools.cpp
  transformational.cpp
  type-code.cpp
  type-info.cpp
  )

if (FLANG_EXPERIMENTAL_CUDA_RUNTIME)
  if (BUILD_SHARED_LIBS)
    message(FATAL_ERROR
      "BUILD_SHARED_LIBS is not supported for CUDA build of Fortran runtime"
      )
  endif()

  enable_language(CUDA)

  # TODO: figure out how to make target property CUDA_SEPARABLE_COMPILATION
  # work, and avoid setting CMAKE_CUDA_SEPARABLE_COMPILATION.
  set(CMAKE_CUDA_SEPARABLE_COMPILATION ON)

  # Treat all supported sources as CUDA files.
  set_source_files_properties(${supported_files} PROPERTIES LANGUAGE CUDA)
  set(CUDA_COMPILE_OPTIONS)
  if ("${CMAKE_CUDA_COMPILER_ID}" MATCHES "Clang")
    # Allow varargs.
    set(CUDA_COMPILE_OPTIONS
      -Xclang -fcuda-allow-variadic-functions
      )
  endif()
  if ("${CMAKE_CUDA_COMPILER_ID}" MATCHES "NVIDIA")
    set(CUDA_COMPILE_OPTIONS
      --expt-relaxed-constexpr
      # Disable these warnings:
      #   'long double' is treated as 'double' in device code
      -Xcudafe --diag_suppress=20208
      -Xcudafe --display_error_number
      )
  endif()
  set_source_files_properties(${supported_files} PROPERTIES COMPILE_OPTIONS
    "${CUDA_COMPILE_OPTIONS}"
    )
endif()

set(FLANG_EXPERIMENTAL_OMP_OFFLOAD_BUILD "off" CACHE STRING
  "Compile Fortran runtime as OpenMP target offload sources (experimental). Valid options are 'off', 'host_device', 'nohost'")

set(FLANG_OMP_DEVICE_ARCHITECTURES "all" CACHE STRING
  "List of OpenMP device architectures to be used to compile the Fortran runtime (e.g. 'gfx1103;sm_90')")

if (NOT FLANG_EXPERIMENTAL_OMP_OFFLOAD_BUILD STREQUAL "off")
  # 'host_device' build only works with Clang compiler currently.
  # The build is done with the CMAKE_C/CXX_COMPILER, i.e. it does not use
  # the in-tree built Clang. We may have a mode that would use the in-tree
  # built Clang.
  #
  # 'nohost' is supposed to produce an LLVM Bitcode library,
  # and it has to be done with a C/C++ compiler producing LLVM Bitcode
  # compatible with the LLVM toolchain version distributed with the Flang
  # compiler.
  # In general, the in-tree built Clang should be used for 'nohost' build.
  # Note that 'nohost' build does not produce the host version of Flang
  # runtime library, so there will be two separate distributable objects.
  # 'nohost' build is a TODO.

  if (NOT FLANG_EXPERIMENTAL_OMP_OFFLOAD_BUILD STREQUAL "host_device")
    message(FATAL_ERROR "Unsupported OpenMP offload build of Flang runtime")
  endif()
  if (BUILD_SHARED_LIBS)
    message(FATAL_ERROR
      "BUILD_SHARED_LIBS is not supported for OpenMP offload build of Fortran runtime"
      )
  endif()

  if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" AND
      "${CMAKE_C_COMPILER_ID}" MATCHES "Clang")

    set(all_amdgpu_architectures
      "gfx700;gfx701;gfx801;gfx803;gfx900;gfx902;gfx906"
      "gfx908;gfx90a;gfx90c;gfx940;gfx1010;gfx1030"
      "gfx1031;gfx1032;gfx1033;gfx1034;gfx1035;gfx1036"
      "gfx1100;gfx1101;gfx1102;gfx1103;gfx1150;gfx1151"
      )
    set(all_nvptx_architectures
      "sm_35;sm_37;sm_50;sm_52;sm_53;sm_60;sm_61;sm_62"
      "sm_70;sm_72;sm_75;sm_80;sm_86;sm_89;sm_90"
      )
    set(all_gpu_architectures
      "${all_amdgpu_architectures};${all_nvptx_architectures}"
      )
    # TODO: support auto detection on the build system.
    if (FLANG_OMP_DEVICE_ARCHITECTURES STREQUAL "all")
      set(FLANG_OMP_DEVICE_ARCHITECTURES ${all_gpu_architectures})
    endif()
    list(REMOVE_DUPLICATES FLANG_OMP_DEVICE_ARCHITECTURES)

    string(REPLACE ";" "," compile_for_architectures
      "${FLANG_OMP_DEVICE_ARCHITECTURES}"
      )

    set(OMP_COMPILE_OPTIONS
      -fopenmp
      -fvisibility=hidden
      -fopenmp-cuda-mode
      --offload-arch=${compile_for_architectures}
      # Force LTO for the device part.
      -foffload-lto
      )
    set_source_files_properties(${supported_files} PROPERTIES COMPILE_OPTIONS
      "${OMP_COMPILE_OPTIONS}"
      )

    # Enable "declare target" in the source code.
    set_source_files_properties(${supported_files}
      PROPERTIES COMPILE_DEFINITIONS OMP_OFFLOAD_BUILD
      )
  else()
    message(FATAL_ERROR
      "Flang runtime build is not supported for these compilers:\n"
      "CMAKE_CXX_COMPILER_ID: ${CMAKE_CXX_COMPILER_ID}\n"
      "CMAKE_C_COMPILER_ID: ${CMAKE_C_COMPILER_ID}")
  endif()
endif()

if (NOT DEFINED MSVC)
  add_flang_library(FortranRuntime
    ${sources}
    LINK_LIBS
    FortranDecimal

    INSTALL_WITH_TOOLCHAIN
  )
else()
  add_flang_library(FortranRuntime
    ${sources}
    LINK_LIBS
    FortranDecimal
  )
  set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded)
  add_flang_library(FortranRuntime.static ${sources}
    LINK_LIBS
    FortranDecimal.static
    INSTALL_WITH_TOOLCHAIN)
  set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreadedDLL)
  add_flang_library(FortranRuntime.dynamic ${sources}
    LINK_LIBS
    FortranDecimal.dynamic
    INSTALL_WITH_TOOLCHAIN)
  set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreadedDebug)
  add_flang_library(FortranRuntime.static_dbg ${sources}
    LINK_LIBS
    FortranDecimal.static_dbg
    INSTALL_WITH_TOOLCHAIN)
  set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreadedDebugDLL)
  add_flang_library(FortranRuntime.dynamic_dbg ${sources}
    LINK_LIBS
    FortranDecimal.dynamic_dbg
    INSTALL_WITH_TOOLCHAIN)
  add_dependencies(FortranRuntime FortranRuntime.static FortranRuntime.dynamic
    FortranRuntime.static_dbg FortranRuntime.dynamic_dbg)
endif()
